package com.zkh.myutils.database.helper;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import com.zkh.myutils.database.DatabaseException;
import com.zkh.myutils.database.dbpool.DBType;
import com.zkh.myutils.database.enums.JoinType;
import com.zkh.myutils.database.enums.Symbol;
import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.Regexp;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

/**
 * 数据表辅助表
 * @author zkh
 */
public class TableHelper {
	//查询列
	private List<String> columnList = new ArrayList<>();
	//新增名更新列
	private List<Relation> operColumnList = new ArrayList<>();
	//数据表名。有别名直接存储为：tablename as alias
	private List<Table> tableList = new ArrayList<>();
	//where条件
	private List<ConditionPart> conditionList = new ArrayList<>();
	//分组
	private List<String> groupList = new ArrayList<>();
	//having条件
	private List<ConditionPart> havingList = new ArrayList<>();
	//排序。格式：[字段名, 排序方式(asc, desc)]
	private List<String[]> orderList = new ArrayList<>();
	//设置获取条数。长度为1：[获取记录数量]；长度为2：[开始记录索引号, 获取记录数量]
	private int[] limit;
	//设置分页
	private int[] page;
	//临时参数
	private List<Object> paramList = new ArrayList<>();
	//数据库类型
	private DBType dbtype;
	
	/******** 构造方法 ********/
	/**
	 * 构造方法。默认数据库MySQL
	 */
	public TableHelper(){
		this.dbtype = DBType.TYPE_MYSQL;
	}
	/**
	 * 构造方法
	 * @param dbtype 数据库类型
	 */
	public TableHelper(DBType dbtype){
		this.dbtype = dbtype;
	}
	
	/******** 设置各部分值 ********/
	/**
	 * 设置查询列，可以包含别名，如：start_time as startTime
	 * @param fields 列名
	 */
	public TableHelper addColumn(String... fields) {
		ArrayUtils.forEach(fields, (e, i)->columnList.add(e));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置操作列。包含insert和update中的列。<br/>
	 * <b>该方法专为批量操作设计，因为批量操作只需要字段名，参数需要单独传递，此处所有值都会默认为null。如果sql中值为函数，要单独调用addOperColumn(String field, Object value, boolean placeholder)来设置</b>
	 * @param fields 字段名
	 */
	public TableHelper addOperColumn(String... fields) {
		for(String field: fields) {
			operColumnList.add(new Relation(field, null, true));
		}
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置操作列。包含insert和update中的列，默认使用占位符
	 * @param field 字段名 
	 * @param value 字段值
	 */
	public TableHelper addOperColumn(String field, Object value) {
		return addOperColumn(field, value, true);
	}
	
	/**
	 * 设置操作列。包含insert和update中的列
	 * @param field 字段名 
	 * @param value 字段值
	 * @param placeholder 是否使用占位符，为特殊值设置，如设置：count=count+1，就不能使用占位符
	 */
	public TableHelper addOperColumn(String field, Object value, boolean placeholder) {
		//处理Date类型值
		if(value instanceof Date) {
			value = new java.sql.Timestamp(((Date) value).getTime());
		}
		operColumnList.add(new Relation(field, value, placeholder));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 添加首张数据表名（或单表查询）
	 * @param tablename 表名，可包含别名，如：table1 as t1
	 */
	public TableHelper addTable(String tablename) {
		if(!tableList.isEmpty()) {
			throw new DatabaseException("多表联表查询，请添加连接类型和ON条件");
		}
		//添加表
		tableList.add(new Table(tablename));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 联表查询从第二张表开始添加表，ON条件字段一律用Symbol.EQ连接，条件之间用AND连接。
	 * @param tablename 表名，可包含别名，如：table2 as t2
	 * @param type 联表类型，通过枚举类获取，不能为空
	 * @param onLeft 第一个ON条件左边。如t1.id
	 * @param onRight 第一个ON条件右边。如t2.table1Id。（合并onLeft后即为t1.id=t2.table1Id）
	 * @param onLeftRights ON条件超过1个时，从第二ON条件开始，依次设置on2Left，on2Right，on3Left，on3Right...，两个参数为一组
	 */
	public TableHelper addTable(String tablename, JoinType type, String onLeft, String onRight, String... onLeftRights) {
		Objects.requireNonNull(type);
		//条件
		ConditionPart[] parts = new ConditionPart[onLeftRights.length/2];
		//其它条件
		for(int i=1,len=onLeftRights.length; i<len; i+=2) {
			parts[(i-1)/2] = new ConditionPart(new Relation(onLeftRights[i-1], onLeftRights[i], false), Symbol.AND);
		}
		//添加表
		return addTable(tablename, type, new ConditionPart(new Relation(onLeft, onRight, false)), parts);
	}
	
	/**
	 * 联表查询从第二张表开始添加表，可以实现任意复杂ON条件
	 * @param tablename 表名
	 * @param type 联表类型，通过枚举类获取，不能为空
	 * @param part1 第一个ON条件，不能为空
	 * @param conditionParts 更多联表条件
	 */
	public TableHelper addTable(String tablename, JoinType type, ConditionPart part1, ConditionPart... conditionParts) {
		Objects.requireNonNull(type);
		Objects.requireNonNull(part1);
		//添加表
		tableList.add(new Table(tablename, type).on(part1).on(conditionParts));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 添加where条件。默认比较符为=，与上一个条件的逻辑符为AND<br />
	 * 注：批处理操作和普通的操作使用的参数不是同一个变量，相互不影响。
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 */
	public TableHelper addCondition(String field, Symbol sign, Object value) {
		return addCondition(field, sign, value, true);
	}
	
	/**
	 * 添加where条件。<br />
	 * 注：批处理操作和普通的操作使用的参数不是同一个变量，相互不影响。
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 * @param placeholder 是否占位
	 */
	public TableHelper addCondition(String field, Symbol sign, Object value, boolean placeholder) {
		return addCondition(field, sign, value, Symbol.AND, placeholder);
	}
	
	/**
	 * 添加where条件。<br />
	 * 注：批处理操作和普通的操作使用的参数不是同一个变量，相互不影响。
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 * @param logic 与前一个条件之间的逻辑
	 * @param placeholder 是否占位
	 */
	public TableHelper addCondition(String field, Symbol sign, Object value, Symbol logic, boolean placeholder) {
		addCondition(field, sign, value, logic, placeholder, false);
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 添加where条件块
	 * @param part 条件块
	 */
	public TableHelper addConditionPart(ConditionPart part) {
		conditionList.add(part);
		return this;
	}
	
	/**
	 * 添加having条件
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 */
	public TableHelper addHaving(String field, Symbol sign, Object value) {
		return addHaving(field, sign, value, true);
	}
	
	/**
	 * 添加where条件。<br />
	 * 注：批处理操作和普通的操作使用的参数不是同一个变量，相互不影响。
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 * @param placeholder 是否占位
	 */
	public TableHelper addHaving(String field, Symbol sign, Object value, boolean placeholder) {
		return addHaving(field, sign, value, Symbol.AND, placeholder);
	}
	
	/**
	 * 添加having条件
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 * @param logic 与前一个条件之间的逻辑
	 * @param placeholder 是否占位
	 */
	public TableHelper addHaving(String field, Symbol sign, Object value, Symbol logic, boolean placeholder) {
		addCondition(field, sign, value, logic, placeholder, true);
		//返回对象本身，以实现链式操作
		return this;
	}

	/**
	 * 添加having条件块
	 * @param part 条件块
	 */
	public TableHelper addHavingPart(ConditionPart part) {
		havingList.add(part);
		return this;
	}
	
	/**
	 * 设置查询列，可以包含别名，如：start_time as startTime
	 * @param groups 列名
	 */
	public TableHelper addGroup(String... groups) {
		ArrayUtils.forEach(groups, (e, i)->groupList.add(e));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置排序字段
	 * @param orderFields 排序字段，全部正序
	 */
	public TableHelper addOrder(String... orderFields) {
		ArrayUtils.forEach(orderFields, (e, i)->orderList.add(new String[] {e, "ASC"}));
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置排序字段。排序方式为：ASC/DESC
	 * @param orderSort 排序字段和方式，依次为：字段1、排序方式、字段2、排序方式、字段3、排序方式。。。
	 */
	public TableHelper addOrderSort(String... orderSort) {
		//遍历
		for(int i=1,len=orderSort.length; i<len; i+=2)
			orderList.add(new String[] {orderSort[i-1], orderSort[i]});
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置数据条数
	 * @param limit 参数。长度为1：[获取记录数量]；长度为2：[开始记录索引号, 获取记录数量]
	 */
	public TableHelper setLimit(int... limit) {
		this.limit = limit;
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置分页
	 * @param pageIndex 当前页码。如果为null或小于1，则重置为1
	 * @param pageSize 每页数据条数。如果为null或小于1，则重置为10，如果大于1000，则重置为1000
	 */
	public TableHelper setPage(Integer pageIndex, Integer pageSize) {
		this.page = new int[] {pageIndex==null || pageIndex<1 ? 1 : pageIndex, pageSize==null || pageSize<1 ? 10 : (pageSize>1000 ? 1000 : pageSize)};
		//返回对象本身，以实现链式操作
		return this;
	}
	
	/**
	 * 设置数据库类型（为临时切换数据源准备）
	 */
	public void setDBType(DBType dbType) {
		this.dbtype = dbType;
	}
	
	/**
	 * 获取设置的分页信息
	 */
	public int[] getPage() {
		return this.page;
	}
	
	/**
	 * 获取数据库类型
	 */
	public DBType getDbType() {
		return this.dbtype;
	}
	
	/** 其它功能方法 **/
	/**
	 * 格式化SQL语句，利于扩展
	 */
	public String formatSQL(String sql){
		return sql.replaceAll("[\\s]+", " ").trim();
	}
	
	/**
	 * 获取查询SQL
	 */
	public String getSelectSql() {
		//查询列为空
		if(columnList.isEmpty()) {
			throw new DatabaseException("请指定查询列名");
		}else if(columnList.size()==1 && "*".equals(columnList.get(0))) {
			throw new DatabaseException("查询列名不能为*，但可以使用“别名.*”的形式");
		}
		//初始化参数
		paramList.clear();
		//预处理limit和page
		int[] limits = limit==null && page==null ? null : (limit==null ? new int[] {(page[0]-1)*page[1],page[1]} : limit);
		//公共组件
		String table = getFormatTable(), condition = getFormatCondition(1), group = getFormatGroup(),
				order = getFormatOrder();
		//判断条件，如果有条数限制，则SQLServer和Oracle的语句和MySQL语句不同
		if(limits!=null && (DBType.TYPE_SQLSERVER==dbtype || DBType.TYPE_ORACLE==dbtype)){
			//limits长度
			int len = limits.length;
			//只限制条数
			if(len==1) {
				//公共SQL
				String commonSql = String.join(",", columnList) + " from " + table + condition + group + order;
				//SQLServer
				if(DBType.TYPE_SQLSERVER==dbtype) {
					return formatSQL("select top " + limits[0] + " " + commonSql);
				}else {//ORACLE
					return formatSQL("select * from (select " + commonSql + ") t where rownum<=" + limits[0]);
				}
			}
			//其余的使用row_number() over()来取
			else {
				//内部SQL
				String innerSql = "select " + String.join(",", columnList)+",row_number() over (" + (order.isEmpty() ? "order by 1" : order) + ") rownumber " + " from " + table + 
						condition + group;
				//返回最终SQL
				return formatSQL("select * from (" + innerSql + ") tmp where rownumber between " + (limits[0]+1) + " and " + (limits[0] + limits[1]));
			}
		}
		//没有分页和limit，或MySQL有limit或分页查询
		else{
			//SQL
			String sql = "select " + String.join(",", columnList) + " from " + table + condition + group + order;
			//limit或分页（此处只有MySQL，不用考虑SQLServer和Oracle）
			if(limits!=null) {
				sql += " limit " + limits[0] + (limits.length==2 ? "," + limits[1] : "");
			}
			//返回SQL
			return formatSQL(sql);
		}
	}
	/**
	 * 获取数据条数SQL
	 */
	public String getCountSql(){
		//初始化参数
		paramList.clear();
		//基础SQL
		String sql = "select count(1) from " + getFormatTable() + getFormatCondition(1);
		//如果分组不为空，记录数必须通过子查询来获取，否则数据是每个分组的记录数。
		if(!groupList.isEmpty()){
			//SQLserver中，group by查询不能直接select *，必需select分组列或聚合函数列
			sql = "select count(1) from (" + sql.replace("count(1)", DBType.TYPE_SQLSERVER==dbtype?String.join(",", groupList):"*") + getFormatGroup() + ") t";
		}
		return formatSQL(sql);
	}
	
	/**
	 * 获取Insert语句
	 */
	public String getInsertSQL() {
		//无字段
		if(operColumnList.isEmpty()) {
			throw new DatabaseException("未检测到新增字段");
		}
		//初始化参数
		paramList.clear();
		//键值容器
		List<String> ks = new ArrayList<>();	//所有字段
		List<Object> vs = new ArrayList<>();	//所有字段值
		//遍历字段
		for(Relation rel: operColumnList) {
			//键
			ks.add(rel.getField());
			//值
			if(rel.isPlaceholder()) {
				vs.add("?"); paramList.add(rel.getValue());
			}else {
				vs.add(rel.getValue());
			}
		}
		//组装SQL
		return "insert into " + getFormatTable() + " (" + String.join(", ", ks) + ") values(" + Utils.join(", ", vs) + ")";
	}

	/**
	 * 获取更新SQL
	 */
	public String getUpdateSQL(){
		//无字段
		if(operColumnList.isEmpty()) {
			throw new DatabaseException("未检测到更新字段");
		}
		//初始化参数
		paramList.clear();
		//设置字段
		List<String> columns = new ArrayList<>();
		//遍历字段
		for(Relation rel: operColumnList) {
			//字段、值
			Object k = rel.getField(), v = rel.getValue();
			//设置
			if(v instanceof String && Regexp.isMatch(" *[\\+\\-\\*\\/] *\\d+(\\.\\d+)? *", v.toString())){
				columns.add(k + "=" + k + v);
			}else if(rel.isPlaceholder()){
				columns.add(k + "=?"); paramList.add(v);
			}else{
				columns.add(k + "=" + v);
			}
		}
		//更新语句
		return "update " + getFormatTable() + " set " + String.join(",", columns) + getFormatCondition(1);
	}
	
	/**
	 * 获取删除SQL。为避免误操作，不允许删除整张表数据。
	 */
	public String getDeleteSQL(){
		//SQL语句
		String sql = "delete from " + getFormatTable();
		//初始化参数
		paramList.clear();
		//条件
		String c = getFormatCondition(1);
		//检测是否有条件
		if(StringUtils.isEmpty(c)){
			throw new DatabaseException("未检测到条件。为避免误操作，禁止删除。");
		}
		sql += c;
		//返回
		return sql;
	}
	
	/**
	 * 获取删除整张表数据SQL
	 */
	public String getDeleteAllSQL(){
		//SQL语句
		return "delete from " + getFormatTable();
	}
	
	/**
	 * 获取参数。无参数size为0。
	 */
	public List<Object> getParamList() {
		return paramList;
	}
	
	/**
	 * 是否已添加表名
	 */
	public boolean isTableEmpty(){
		return tableList.isEmpty();
	}
	
	/**
	 * 是否已设置select字段
	 */
	public boolean isColumnEmpty() {
		return columnList.isEmpty();
	}
	
	/**
	 * 清除所有已设置数据。相当于一个全新的TableHelper实例
	 */
	public void clearAll() {
		columnList.clear();	//查询列
		operColumnList.clear();	//操作列
		tableList.clear();		//表名
		conditionList.clear();		//where条件
		groupList.clear();		//分组
		havingList.clear();	//having条件
		orderList.clear();		//排序
		paramList.clear();		//参数
		limit=null;page=null;	//条件限制和分页
	}
	
	/**内部方法**/
	/**
	 * 添加条件。<br />
	 * 注：批处理操作和普通的操作使用的参数不是同一个变量，相互不影响。
	 * @param field 键
	 * @param sign 比较符
	 * @param value 值
	 * @param logic 与前一个条件之间的逻辑
	 * @param placeholder 是否占位
	 * @param isHaving 是否having条件
	 */
	private void addCondition(String field, Symbol sign, Object value, Symbol logic, boolean placeholder, boolean isHaving) {
		//获取容器
		List<ConditionPart> list = isHaving ? havingList : conditionList;
		//如果是第一个条件块，就将逻辑连接符置为空
		if(list.isEmpty()) {
			logic = null;
		}
		//保存条件块
		list.add(new ConditionPart(new Relation(field, sign, value, placeholder), logic));
	}
	
	/**
	 * 获取拼接表名
	 */
	private String getFormatTable(){
		//校验表名
		if(tableList.isEmpty()) throw new DatabaseException("未添加表名");
		//表容器
		StringBuilder table = new StringBuilder();
		//遍历表
		for(Table t: tableList) {
			table.append(t.getTableString(paramList)).append(' ');
		}
		return table.toString();
	}
	
	/**
	 * 获取格式化条件
	 * @param type 类型。1：where条件，2：having条件
	 */
	private String getFormatCondition(int type) {
		//解析
		ConditionPartResult result = ConditionPartResult.getConditionPartString(type==1 ? conditionList : havingList);
		//保存参数
		paramList.addAll(result.getParamList());
		//返回条件SQL
		return result.isEmpty() ? "" : (type==1 ? " where " : " having ") + result.getCondition();
	}
	
	/**
	 * 获取分组
	 */
	private String getFormatGroup(){
		//分组
		String group = groupList.isEmpty() ? "" : " group by " + String.join(",", groupList);
		//有分组
		if(!group.isEmpty()){
			//having
			group += getFormatCondition(2);
		}
		//返回
		return group;
	}

	/**
	 * 获取格式化后的排序
	 */
	private String getFormatOrder(){
		//排序SQL
		StringBuffer sb = new StringBuffer();
		//遍历排序字段
		orderList.forEach(odr->sb.append(",").append(odr[0]).append(" ").append(odr[1]));
		//返回排序
		return sb.length()==0 ? "" : " order by " + sb.substring(1);
	}
}
